import numpy as np
def step_function(x):
    return np.array(x > 0, dtype=int)

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def Tanh(x):
    return 1-np.exp(-2*x)/(1+np.exp(-2*x))

def ReLU(x):
    return np.maximum(0, x)

def softmax(x):
    if x.ndim == 2:
        x = x-np.max(x, axis=1, keepdims=True) # 溢出对策
        return np.exp(x) / np.sum(np.exp(x), axis=1, keepdims=True)
    x = x-np.max(x) # 溢出对策
    return np.exp(x) / np.sum(np.exp(x))

def Swish(x): # 相当于sigmoid(x)*x
    return x/(1+np.exp(-x))
def Identity(x):
    return x

## 二分类选择Simoid
## 多分类选择softmax
## 回归选择ReLU 或者Swish


## 损失函数
#MSE 均方误差 适用于回归问题
def mean_squared_error(y, t):

    """
    计算均方误差（Mean Squared Error, MSE）

    参数:
    y -- 预测值数组
    t -- 真实值数组

    返回:
    计算得到的均方误差值
    """
    return np.sum((y-t)**2) / 2.0  # 计算预测值与真实值差的平方和，然后除以2

# 交叉熵 应用在多分类问题
def cross_entropy_error(y, t):

    """
    计算交叉熵误差函数

    参数:
    y : 预测输出，通常是一个numpy数组，表示神经网络的输出
    t : 真实标签，可以是one-hot编码形式也可以是索引形式

    返回:
    交叉熵误差值

    注意:
    1. 当输入是一维数组时，会将其转换为二维数组
    2. 当标签是one-hot编码时，会将其转换为索引形式
    3. 添加了一个小的常数1e-7防止log(0)的情况
    """
    if y.ndim == 1:  # 如果输入是一维数组
        t = t.reshape(1, t.size)  # 将标签t转换为二维数组
        y = y.reshape(1, y.size)  # 将预测y转换为二维数组
    if t.size == y.size:  # 如果标签是one-hot编码形式
        t = t.argmax(axis=1)  # 将one-hot编码转换为索引形式
    batch_size = y.shape[0]  # 获取批量大小
    # 计算交叉熵误差，使用np.log计算对数，并添加一个小的常数防止log(0)
    return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7))

# 数值微分
def numerical_diff(f, x):
    h = 1e-4  # 0.0001
    return (f(x + h) - f(x - h)) / (2 * h)


#数值微分计算梯度 这里的x是一个一维的数组 ，返回的也是一维的数组 对于多维数组 需要循环计算
def _numerical_gradient(f, x):

    """
    计算函数f在x处的数值梯度

    参数:
    f: 需要计算梯度的函数
    x: numpy数组，梯度的计算点

    返回:
    grad: numpy数组，与x形状相同的梯度
    """
    h = 1e-4  # 微小的变化量，用于计算数值微分
    grad = np.zeros_like(x)  # 初始化梯度数组，形状与x相同
    for i in range(x.size):  # 遍历x的每个元素
        tmp = x[i]
        x[i] = tmp + h
        fxh1 = f(x)
        x[i] = tmp - h
        fxh2 = f(x)
        grad[i] = (fxh1 - fxh2) / (2 * h)
        x[i] = tmp
    return grad

def numerical_gradient(f, x):
    """
    计算函数f在x处的数值梯度

    参数:
    f: 需要计算梯度的函数
    x: numpy数组，梯度的计算点

    返回:
    grad: numpy数组，与x形状相同的梯度
    """
    if x.ndim == 1:
        return _numerical_gradient(f, x)
    else:
        grad = np.zeros_like(x)
        for idx, x_row in enumerate(x):
            grad[idx] = _numerical_gradient(f, x_row)
        return grad

if __name__ == '__main__':
    x = np.array([[-1.0, 1.0, 2.0], [-2.0, 2.0, 0.0], [1.0, 2.0, -1.0]])
    y = softmax(x)
    print(y)